package com.gitee.feizns.dynamic.bean;

import com.gitee.feizns.dynamic.Ex;
import com.gitee.feizns.dynamic.Objs;
import com.gitee.feizns.dynamic.convert.Converter;
import com.gitee.feizns.dynamic.convert.Converts;
import com.gitee.feizns.dynamic.reflect.Types;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;

/**
 * <b>代表一个类上某个属性的元信息</b>
 * <p>
 *     如果有类class User { public String getName(){return null;} }<br/>
 *     要代表User类的name属性也就是 Prop<String, User> nameProp = Prop.of(User.class, "name");<br/>
 *     因为该属性仅有一个getName();代表该属性是可读的但是不可写
 * </p>
 * <pre>{@code
 *  //这个属性代表这是User.class类型的name属性
 *  //String是name属性的数据类型，User则代表该属性所属的类型
 *  Prop<String, User> nameProp = Prop.of(User.class, "name")
 * }</pre>
 * @param <D> 属性数据类型
 * @param <S> 属性源对象的类型
 * @author feizns
 * @since 2019/6/5
 */
public interface Prop<D, S> extends AnnotatedElement, Cloneable {

    /**
     * <b>获取属性值</b>
     * <pre>{@code
     *  static class User {
     *      String name;
     *      public User(String name){ this.name = name; }
     *      public String getName() { return this.name; }
     *  }
     *  Prop.of(new User("张三"), "name").val();  //"张三"
     * }</pre>
     * @return 返回属性的值
     */
    default D val() {
        return invokeReadMethod();
    }

    /**
     * <b>获取属性值</b>
     * <pre>{@code
     *  static class User {
     *      Integer name;
     *      public User(Integer name){ this.name = name; }
     *      public Integer getName() { return this.name; }
     *  }
     *  Prop.of(new User(1), "name").val(String.class);  //"1"
     * }</pre>
     * @apiNote 会尝试将原数据类型转换为期望得到的数据类型
     * @param targetType 期望得到的属性值类型Class
     * @param <R> 期望返回的属性值类型
     * @return 返回对应类型的值
     */
    default <R> R val(Class<R> targetType) {
        D value = val();
        return value != null ? Converts.to(value, targetType) : null;
    }

    /**
     * 值列表（对于原来是数组或是Iterable的对象进行转换为List，对于非集合的数据返回一个数据的数组）
     * @param targetType 目标类型
     * @return {@link List }<{@link R }>
     */
    @SuppressWarnings("unchecked")
    default <R> List<R> list(Class<R> targetType) {
        return (List<R>) new ArrayList<>(java.util.Arrays.asList(Objs.toArray(val(targetType))));
    }

    /**
     * 值列表（对于原来是数组或是Iterable的对象进行转换为List，对于非集合的数据返回一个数据的数组）
     * @return {@link List }<{@link R }>
     */
    @SuppressWarnings("unchecked")
    default <R> List<R> list() {
        return (List<R>) new ArrayList<>(Arrays.asList(Objs.toArray(val())));
    }

    /**
     * <b>判断当前属性值是否为null</b>
     * @return 是/否
     */
    default boolean isNull() {
        return Objects.isNull(val());
    }

    /**
     * 判断当前属性是否是一个数组
     * @return boolean
     */
    default boolean isArray() {
        Class<?> returnType = fieldClassOrReadMethodReturnType();
        return returnType != null && returnType.isArray();
    }

    /**
     * 判断当前属性是否是一个Iterable
     * @return boolean
     */
    default boolean isIterable() {
        Class<?> returnType = fieldClassOrReadMethodReturnType();
        return returnType != null && instanceOf(Iterable.class);
    }

    /**
     * 判断当前属性是否是一个JavaBean
     * @return boolean
     */
    default boolean isJavaBeanType() {
        Class<?> returnType = fieldClassOrReadMethodReturnType();
        return returnType != null && Types.isJavaBeanType(returnType);
    }

    /**
     * instanceof 任意一个
     * @param types 类型
     * @return boolean
     */
    default boolean instanceOf(Class<?>... types) {
        Class<?> returnType = fieldClassOrReadMethodReturnType();
        return java.util.Arrays.stream(types).anyMatch(type -> type.isAssignableFrom(returnType));
    }

    /**
     * <b>为属性赋值</b>
     * <p>
     *     获取旧值并设置新值
     * </p>
     * @param newVal 属性新值
     * @return oldVal 返回旧值
     */
    default D getAndSet(D newVal) {
        D oldVal = val();
        set(newVal);
        return oldVal;
    }

    /**
     * <b>设置属性</b>
     * <p>
     *     将类型不兼容的将会启用自动类型转换
     * </p>
     * @param newVal 新值
     * @return 为属性设置新值
     */
    default Prop<D, S> set(Object newVal) {
        return set(newVal, Converts::to);
    }

    /**
     * <b>设置属性值类型和属性的类型不一致时、指定类型转换规则</b>
     * <p>
     *     如果转换器、不支持转换、则不会调用set函数
     * </p>
     * @see Converter
     * @see Converter#support(Object, Class)
     * @see Converter#to(Object, Class)
     * @param newVal 新值
     * @param converter 转换器
     * @return 属性
     * @param <T> 设置的值类型
     */
    @SuppressWarnings("unchecked")
    default <T> Prop<D, S> set(T newVal, Converter<T, D> converter) {
        Class<?> writeMethodParameterType = writeMethodParameterType();
        if ( writeMethodParameterType != null ) {
            if ( converter.support(newVal, writeMethodParameterType) ) {
                D realNewVal = converter.to(newVal, (Class<D>) writeMethodParameterType);
                return invokeWriteMethod(realNewVal);
            }
        }
        return this;
    }

    /**
     * <b>调用读取属性方法</b>
     * @return 属性本身
     */
    @SuppressWarnings("unchecked")
    default D invokeReadMethod() {
        S source = getSource();
        Method readMethod = readMethod();
        if ( source != null && readMethod != null )
            return Ex.tryCatch(() -> (D) readMethod.invoke(source)).orElse(null);
        return null;
    }

    /**
     * <b>调用写方法</b>
     * @param newVal 新值
     * @return 属性本身
     */
    default Prop<D, S> invokeWriteMethod(D newVal) {
        S source = getSource();
        Method writeMethod = writeMethod();
        if ( source != null && writeMethod != null )
            Ex.tryCatch(() -> writeMethod.invoke(source, newVal));
        return this;
    }

    /**
     * <b>对当前属性做一次尝试从source值对象获取值并赋值给当前对象的操作</b>
     * <p>
     *     将当前属性、期望从source中获取的第一个非空属性值赋值给当前属性
     * </p>
     * @param source 值对象
     * @return 为属性设置新值
     */
    default Prop<D, S> tryGet(Object source) {
        Props.tryGet(this, source);
        return this;
    }

    /**
     * <b>将当前属性的属性值作为原对象获取其子属性</b>
     * <p>（该方法获取属性值的属性）</p>
     * <pre>{@code
        @Data
        class User {
            Integer id;
            User child;
            User(Integer id) { this.id = id; }
        }
        public static void main(String[] args) {
            User one = new User(1);
            User two = new User(2);
            User three = new User(3);
            one.child = two;
            two.child = three;
            Prop<User, User> prop = Prop.of(one, "child");
            System.out.println(one == prop.getTarget());        //true
            System.out.println(two == prop.val());              //true
            Prop<User, User> threeProp = prop.get("child");
            System.out.println(threeProp.getTarget() == two);   //true
            System.out.println(threeProp.val() == three);       //true
        }
     * }</pre>
     * @param name 属性名
     * @param <P> 子属性的数据类型
     * @return 返回对应名称的属性信息
     */
    default <P> Prop<P, D> get(String name) {
        return of(val(), name);
    }

    /**
     * <b>获取属性</b>
     * <p>该方法获取属性值的属性</p>
     * <p>（如果属性是一个只写属性，则需要通过propertyClass指名writeMethod的参数类型）</p>
     * @param name 属性名称
     * @param propertyClass 属性Class类型
     * @param <P> 属性真实类型
     * @return 返回对应名称的属性信息
     */
    default <P> Prop<P, D> get(String name, Class<P> propertyClass) {
        return of(val(), name, propertyClass);
    }

    /**
     * <b>获取属性</b>
     * <p>（假设该属性已属于某个对象，则可以通过该方法获取属性的子属性）</p>
     * @param getter 属性的get方法引用
     * @param <P> 属性类型
     * @return 返回对应get方法引用的属性信息
     */
    default <P> Prop<P, D> get(Getter<D, P> getter) {
        return of(val(), getter);
    }

    /**
     * <b>属性名</b>
     * @return 返回一个属性的属性名
     */
    String name();

    /**
     * <b>是否可读</b>
     * @return 如果属性有读方法（get方法）则可读, 返回true。否则返回false
     */
    default boolean isReadable() {
        return readMethod() != null;
    }

    /**
     * <b>是否可写</b>
     * @return 如果属性有写方法（set方法）则可写, 返回true。否则返回false
     */
    default boolean isWritable() {
        return writeMethod() != null;
    }

    /**
     * <b>判断当前对象是否用户源对象实例</b>
     * @return 是/否
     */
    default boolean hasSource() {
        return getSource() != null;
    }

    /**
     * <b>获取源对象</b>
     * @return 返回该属性是源对象。（该属性属于这个source对象）
     */
    S getSource();

    /**
     * <b>获取属性所属对象的类型</b>
     * <pre>
     *     Prop<String, User> prop = Prop.of(user1, "name");
     *     System.out.println(prop.getSourceClass());   //User.class
     * </pre>
     * @return 返回属性所属对象的类型
     */
    @SuppressWarnings("unchecked")
    default Class<S> getSourceClass() {
        return getSource() != null ? (Class<S>) getSource().getClass() : null;
    }

    /**
     * <b>获取字段</b>
     * @return 返回存储数据的字段
     */
    Field field();

    /**
     * <b>获取属性的字段{@link Class}类型</b>
     * @return {@link Class}
     */
    default Class<?> fieldClass() {
        return Optional.ofNullable(field()).map(Field::getType).orElse(null);
    }

    /**
     * <b>默认获取属性类型、没有属性则获取Get方法类型</b>
     * @return {@link Class}
     */
    default Class<?> fieldClassOrReadMethodReturnType() {
        Class<?> type = fieldClass();
        if ( type == null )
            type = Optional.ofNullable(readMethod()).map(Method::getReturnType).orElse(null);
        return type;
    }

    /**
     * <b>获取属性的字段类型</b>
     * @return {@link Type}
     */
    default Type fieldType() {
        return Optional.ofNullable(field()).map(Field::getGenericType).orElse(null);
    }

    /**
     * <b>获取属性的类型信息</b>
     * <p>
     *     属性上获取不到则到get方法上获取
     * </p>
     * @return {@link Type}
     */
    default Type fieldTypeOrReadMethodGenericReturnType() {
        Type type = fieldType();
        if ( type == null )
            type = Optional.ofNullable(readMethod()).map(Method::getGenericReturnType).orElse(null);
        return type;
    }

    /**
     * <b>读方法（对应属性的GET方法）</b>
     * <p>（如果是属性不可读则返回null）</p>
     * <p>假设属性是name则对应的读方法为getName()</p>
     * @return 返回读取数据的方法
     */
    Method readMethod();

    /**
     * <b>获取读方法的返回值类型</b>
     * @return 读方法的返回值类型
     */
    default Class<?> readMethodReturnType() {
        Method readMethod = readMethod();
        return readMethod != null ? readMethod.getReturnType(): null;
    }

    /**
     * <b>写方法（对应属性的SET方法）</b>
     * <p>假设属性是name则对应的读方法为setName()</p>
     * @apiNote 如果是属性不可写则返回null
     * @return 返回写入数据的方法
     */
    Method writeMethod();

    /**
     * <b>写入函数的参数类型</b>
     * @return 写入函数的类型
     */
    default Class<?> writeMethodParameterType() {
        Method writeMethod = writeMethod();
        return writeMethod != null ? writeMethod.getParameterTypes()[0] : null;
    }

    /**
     * <b>查找一个属性上的注解，默认按照【字段、读方法、写方法】的顺序去查找</b>
     * <p>例：如果一个属性name，它的字段和getName()方法上都有@JsonIngore注解则返回字段上的注解信息</p>
     * @param annotationClass 注解类型
     * @param <T> 实际注解类型
     * @return {@link java.lang.annotation.Annotation}
     * @throws NullPointerException 如果参数annotationClass为空
     */
    @Override
    default <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
        Stream<AnnotatedElement> elements = annotatedElements().filter(item -> item.isAnnotationPresent(annotationClass));
        Optional<AnnotatedElement> annotatedElement = elements.findFirst();
        return annotatedElement.map(element -> element.getAnnotation(annotationClass)).orElse(null);
    }

    /**
     * <b>获取从属性的字段、读方法、写方法上三者上的所有注解（不包含私有元素的注解）</b>
     * <p>
     *     例：如果存在一个属性name，它的字段field是使用private修饰的，读方法getName()则是使用public修饰.
     *     如：@Autowired private String name;和 @JsonIgnore public String getName() 则仅返回
     *     [@JsonIgnore]
     * </p>
     * @see #getDeclaredAnnotations() 如果需要返回私有字段上的可以看
     * @return 按照字段、读方法、写方法的顺序放置三者的所有注解在返回值数组中
     */
    @Override
    default Annotation[] getAnnotations() {
        return annotatedElements().flatMap(item -> java.util.Arrays.stream(item.getAnnotations())).toArray(Annotation[]::new);
    }

    /**
     * <b>获取从属性的字段、读方法、写方法上三者上的所有注解（含私有元素的注解）</b>
     * <p>
     *     例：如果存在一个属性name，它的字段field上有注解@Autowired，读方法getName()上有@JsonIgnore.
     *     如：@Autowired private String name;和 @JsonIgnore public String getName() 则返回
     *     [@Autowired, @JsonIgnore]
     * </p>
     * @see #getAnnotations() 如果不需要返回私有成员上的注解可以看
     * @return 按照字段、读方法、写方法的顺序放置三者的所有注解在返回值数组中
     */
    @Override
    default Annotation[] getDeclaredAnnotations() {
        return annotatedElements().flatMap(item -> java.util.Arrays.stream(item.getDeclaredAnnotations())).toArray(Annotation[]::new);
    }

    /**
     * <b>属性上的元素信息=【字段元信息、读方法元信息、写方法元信息】</b>
     * @return 返回一个装载了字段、读方法、写方法的流
     */
    default Stream<AnnotatedElement> annotatedElements() {
        return java.util.Arrays.stream(new AnnotatedElement[]{ field(), readMethod(), writeMethod() }).filter(Objects::nonNull);
    }

    /**
     * 以当前属性的元数据构建属性.
     * @param source 源
     * @return {@link Prop}<{@link D}, {@link S}>
     */
    Prop<D, S> of(S source);

    /**
     * <b>根据目标类型，属性名构建一个属性</b>
     * <pre>{@code
     *  Prop<String, User> prop = Prop.of(new User("张三"), "name")
     *  System.out.println(prop.val());      //"张三"
     * }</pre>
     * @param target 属性源对象
     * @param name 属性名称
     * @param <O> 属性的源对象类型
     * @param <D> 属性的数据类型
     * @return 属性信息
     */
    static <D, O> Prop<D, O> of(O target, String name) {
        return new DefaultPropImpl<>(target, name);
    }

    /**
     * <b>根据目标类型，GET方法引用构建一个属性</b>
     * <pre>{@code
     *  Prop<String, User> prop = Prop.of(new User("张三"), User::getName)
     *  System.out.println(prop.val());      //"张三"
     * }</pre>
     * @param target 属性源对象
     * @param getter 属性GET方法的方法引用
     * @param <O> 属性的源对象类型
     * @param <D> 属性的数据类型
     * @return 属性信息
     */
    static <D, O> Prop<D, O> of(O target, Getter<O, D> getter) {
        return new DefaultPropImpl<>(target, getter);
    }

    /**
     * <b>根据目标类型，属性名称构建一个属性</b>
     * <p>如果属性是一个只写属性（也就不会有field和readMethod），则需要通过propertyClass指名writeMethod的参数类型</p>
     * <pre>{@code
     *  Prop<String, User> prop = Prop.of(new User("张三"), "name", String.class)
     *  System.out.println(prop.val());      //"张三"
     *  prop.set("李四");
     *  System.out.println(prop.val());      //"李四"
     * }</pre>
     * @param target 属性所属源对象
     * @param name 属性名称
     * @param <O> 属性源对象的类型
     * @param <D> 属性数据的类型
     * @return 属性信息
     */
    static <D, O> Prop<D, O> of(O target, String name, Class<D> propertyClass) {
        return new DefaultPropImpl<>(target, name, propertyClass);
    }


    /**
     * <b>根据目标类型，属性名称构建一个属性</b>
     * <pre>{@code
     * Prop<String, User> prop = Prop.of(User.class, "name")
     * System.out.println(prop.val());      //"张三"
     * }</pre>
     * @param targetClass 属性原对象的Class
     * @param name 属性名称
     * @param <O> 属性源对象的类型
     * @param <D> 属性数据的类型
     * @return 属性信息
     */
    static <D, O> Prop<D, O> of(Class<O> targetClass, String name) {
        return new DefaultPropImpl<>(targetClass, name);
    }

    /**
     * <b>根据目标类型，getter方法引用构建一个属性</b>
     * <pre>{@code
     *  Prop<String, User> prop = Prop.of(User.class, User::getName)
     *  System.out.println(prop.val());     //"张三"
     * }</pre>
     * @param targetClass 目标类型Class
     * @param getter getter方法引用
     * @param <O> 属性源对象的类型
     * @param <D> 属性数据的类型
     * @return 属性信息
     */
    static <D, O> Prop<D, O> of(Class<O> targetClass, Getter<O, D> getter) {
        return new DefaultPropImpl<>(targetClass, getter);
    }

    /**
     * <b>根据目标类型，属性名，属性类型构建一个属性</b>
     * <p>（如果属性是一个只写属性（也就不会有field和readMethod），则需要通过propertyClass指名writeMethod的参数类型）</p>
     * <pre>{@code
     *  Prop<String, User> prop = Prop.of(User.class, "name", String.class)
     *  System.out.println(prop.val());      //"张三"
     *  prop.set("李四");
     *  System.out.println(prop.val());      //"李四"
     * }</pre>
     * @param targetClass 目标类型Class
     * @param name 属性名称
     * @param propertyClass 属性类型Class
     * @param <O> 目标对象类型
     * @param <D> 属性类型
     * @return 属性信息
     */
    static <D, O> Prop<D, O> of(Class<O> targetClass, String name, Class<D> propertyClass) {
        return new DefaultPropImpl<>(targetClass, name, propertyClass);
    }

}

